package tjs.study.notes.dotest.jvm.classload;

import sun.misc.Launcher;
import sun.misc.URLClassPath;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;

/**
 * Created by Nicky.Tang on 2021/10/9 4:07 下午
 * 参考博客
 *
 * @see https://blog.csdn.net/briblue/article/details/54973413
 * @since 02.12.10
 */
public class DoTestOfClassLoad {
    //fixme 父加载器不是父类

    /**
     * *三大核心类加载器，每一个类加载器，固定只加载指定的路径
     *
     * @see Launcher.AppClassLoader          //System.getProperty("java.class.path")//即当前项目的工作目录
     * @see Launcher.ExtClassLoader          //System.getProperty("java.ext.dirs")
     * @see BootstrapClassLoader (java不可见) //System.getProperty("sun.boot.class.path")
     * <p>
     * *Launcher加载核心代码
     * @see Launcher#Launcher()
     * //ClassLoader extcl;
     * //extcl = ExtClassLoader.getExtClassLoader();
     * //loader = AppClassLoader.getAppClassLoader(extcl);
     * 所以 ExtClassLoader.getParent()=null
     * AppClassLoader.getParent()=ExtClassLoader
     * <p>
     * *ExtClassLoader的parent为什么为null，但是可以理解成为BootstrapClassLoader
     * @see ClassLoader#loadClass(java.lang.String, boolean)
     * //Class<?> c = findLoadedClass(name);//已加载，缓存中是否存在
     * //if (c == null) {
     * //  if (parent != null) {
     * //      c = parent.loadClass(name, false);//有parent就递归调用
     * //  } else {
     * //      c = findBootstrapClassOrNull(name); //fixme 父加载器为空，则使用BootstrapClassLoader加载
     * //  }
     * //}
     * //if (c == null) { c = findClass(name); }
     * @see Launcher.AppClassLoader#loadClass(java.lang.String, boolean)
     * @see ClassLoader#loadClass(java.lang.String, boolean)
     * @see Launcher.ExtClassLoader#loadClass(java.lang.String, boolean)【start】
     * @see ClassLoader#findBootstrapClassOrNull(java.lang.String)//null
     * @see Launcher.ExtClassLoader#findClass(java.lang.String)//ClassNotFoundException
     * @see Launcher.ExtClassLoader#loadClass(java.lang.String, boolean)【end】
     * @see Launcher.AppClassLoader#findClass(java.lang.String)
     * <p>
     * * 自定义classload为什么不建议重写loadClass方法，而是重写findClass
     * 因为重写了loadClass方法，可能没法保证双亲委派机制（但是AppClassLoader/ExtClassLoader都有重写，不过也都有调用super.loadClass）
     * <p>
     * *classLoader的getParent
     * 一个ClassLoader创建时如果没有指定parent，那么它的parent默认就是getSystemClassLoader() -> AppClassLoader
     * @see ClassLoader#ClassLoader()
     * ( this(checkCreateClassLoader(), getSystemClassLoader());)
     * @see ClassLoader#getSystemClassLoader()
     * @see ClassLoader#initSystemClassLoader()
     * @see Launcher#getClassLoader()
     * @see Launcher#Launcher()
     *
     * ( this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);)
     */
    public static void main(String[] args) throws Exception {
        makeAllUseSelfClassLoader(args);

//        debugLoadClass();
//        printParentClassloader();
//        testClassLoaderPath();
        //System.out.println(Launcher.class.getClassLoader());
    }

    public static void makeAllUseSelfClassLoader(String[] args) throws Exception {
        System.out.println("DoTestOfClassLoad的类加载器：" + DoTestOfClassLoad.class.getClassLoader());
        System.out.println("OtherPackagerUrlClassLoader的类加载器：" + OtherPackagerUrlClassLoader.class.getClassLoader());
        if (OtherPackagerUrlClassLoader.firstFlag) {
            System.out.println("首次启动，重置main的类加载器");
            OtherPackagerUrlClassLoader.firstFlag = false;

            URL[] appClassLoaderURLs = getAppClassLoaderURLs();//使用appClassLoader的加载路径
            OtherPackagerUrlClassLoader urlClassLoader = new OtherPackagerUrlClassLoader(appClassLoaderURLs);
            Class<?> mainClass = Class.forName(DoTestOfClassLoad.class.getName(), false, urlClassLoader);
            //Class<?> mainClassRestart = urlClassLoader.loadClass(mainClass.getName(), false);
            Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
            mainMethod.invoke(null, new Object[]{args});//直接传args，表示多个参数；而实际上只有一个，所以要转成Object
        }
    }

    public static URL[] getAppClassLoaderURLs() throws Exception {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        // appClassLoader的加载路径存储在ucp属性中
        Field ucpField = classLoader.getClass().getDeclaredField("ucp");
        ucpField.setAccessible(true);
        URLClassPath classPath = (URLClassPath) ucpField.get(classLoader);
        return classPath.getURLs();
    }

    public static void debugLoadClass() {
//        UserService userService = new UserService();
    }

    public static void printParentClassloader() {
        System.out.println(DoTestOfClassLoad.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(DoTestOfClassLoad.class.getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@3b07d329
        System.out.println(DoTestOfClassLoad.class.getClassLoader().getParent().getParent());//null
        System.out.println(long.class.getClassLoader());//null
        System.out.println(Launcher.class.getClassLoader());//null
    }

    public static void testClassLoaderPath() {
        /**
         * @see Launcher.BootClassPathHolder#BootClassPathHolder()
         *      File[] var1 = Launcher.getClassPath(Launcher.bootClassPath);
         */
        System.out.println("sun.boot.class.path\n" + System.getProperty("sun.boot.class.path") + "\n");

        /**
         * @see Launcher.ExtClassLoader#getExtDirs()
         */
        System.out.println("java.ext.dirs\n" + System.getProperty("java.ext.dirs") + "\n");

        /**
         * @see Launcher.AppClassLoader#getAppClassLoader(ClassLoader)
         * 这个路径其实就是当前java工程目录bin，里面存放的是编译生成的class文件。
         * /Users/piangpiang/Documents/my-workspace/my-mayun/tjs-study-notes-test2/tjs-study-notes/tjs-study-notes/tjs-study-dotest/target/classes
         */
        System.out.println("java.class.path\n" + System.getProperty("java.class.path") + "\n");
    }
}
